﻿//   CodeGenerator.cs
//
//   Copyright 2009 Eric Llewellyn
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace LibEELbeta
{
    public class CodeGenerator
    {
        private int labelNo;
        private List<Instruction> code;

        public CodeGenerator()
        {
            labelNo = 0;
        }

        public Instruction[] GetInstructions(SyntaxNode[] syntaxNodes)
        {
            code = new List<Instruction>();

            foreach (SyntaxNode n in syntaxNodes)
                Write(n);

            return code.ToArray();
        }

        //--------------------------------

        //Returns a label name l(n) where
        //(n) is incremented each time this
        //function is called
        private string GetNextLabel()
        {
            labelNo++;
            return "l" + labelNo.ToString();
        }

        //coverts the operands of a push instruction
        //from string representations to actual values
        //ie. the operand of the instruction "PUSH 3.14"
        //    would become a double
        private object ConvertValue(string value)
        {
            double tmp;

            if (value.ToUpper() == "NULL")
                return null;
            else if (value.ToUpper() == "TRUE" || value.ToUpper() == "FALSE")
                return Convert.ToBoolean(value);
            else
            {
                try
                {
                    tmp = Double.Parse(value);
                    return tmp;
                }
                catch
                {
                    if (value == "[]")
                        return new Dictionary<object, object>();
                    else if (value.StartsWith("\"") && value.EndsWith("\""))
                        return value.Substring(1, value.Length - 2);
                    else
                        return value;
                }
            }
            
            
        }

        //Constant{value}                           Push        value
        //Variable{$value}                          Push        ConvertValue($value)
        //                                          GLoad
        //Variable{value}                           Push        ConvertValue(value)
        //                                          Load
        //Table{0,1}        Write(0);   Write(1);   DLoad
        //Add{0,1}          Write(0);   Write(1);   Add
        //Sub{0,1}          Write(0);   Write(1);   Sub
        //Mul{0,1}          Write(0);   Write(1);   Mul
        //Div{0,1}          Write(0);   Write(1);   Div
        //Neg{0}            Write(0);               Neg
        //Read{0}           Write(0);               Read
        //Write{0}          Write(0);               Write
        //Assign{Variable,$value}       Write(1);   Push        ConvertValue($value)
        //                  Write(0);
        //Assign{Variable,value}        Write(1);   Push        ConvertValue(value)
        //                  Write(0);
        //Assign{Table,1}               Write(1);
        //                  Write(0[1]) Write(0[0]) DStore
        //                  Write(0);
        //If{0,1,2}         Write(0);               Push        lTrue
        //                                          Jt
        //                                          Push        lFalse
        //                                          Jmp
        //                                          Label       lTrue
        //                  Write(1);               Push        lEnd
        //                                          Jmp
        //                                          Label       lFalse
        //                  Write(2);               Label       lEnd
        //Loop{0 1}                                 Label       lTop
        //                  Write(0);   Write(1);   Push        lTop
        //                                          Jt
        //Eq{0,1}           Write(0);   Write(1);   Eq
        //NEq{0,1}          Write(0);   Write(1);   Eq
        //                                          Not
        //Lt{0,1}           Write(0);   Write(1);   Lt
        //Ge{0,1}           Write(0);   Write(1);   Lt
        //                                          Not
        //Le{0,1}           Write(0);   Write(1);   Le
        //Gt{0,1}           Write(0);   Write(1);   Le
        //                                          Not
        //Not{0}            Write(0);               Not
        //Statements{0 [1]} Write(0);   [Write(1);]
        //Discard{0}        Write(0);               Pop
        //Function{0}                               Push        lEnd
        //                                          Jmp
        //                                          Label       lFBegin
        //                              Write(0)    Ret
        //                                          Label       lFend
        //                                          Push        lFBegin
        //Call{0 [1]}       [Write(1);] Write(0);   Call 
        //Arg{value}                                Push        ConvertValue(value)
        //                                          Store
        private void Write(SyntaxNode root)
        {
            object pushVal;
            switch (root.nodeType)
            {
                case SyntaxNodeTypes.Constant:
                    code.Add(new Instruction(InstructionCodes.Push, ConvertValue(root.value)));
                    break;
                case SyntaxNodeTypes.Variable:
                    pushVal = ConvertValue(root.value);
                    code.Add(new Instruction(InstructionCodes.Push, pushVal));
                    if (pushVal is string && ((string)pushVal).StartsWith("$"))
                        code.Add(new Instruction(InstructionCodes.GLoad, null));
                    else
                        code.Add(new Instruction(InstructionCodes.Load, null));
                    break;
                case SyntaxNodeTypes.Table:
                    Write(root.operands[1]);
                    Write(root.operands[0]);
                    code.Add(new Instruction(InstructionCodes.DLoad, null));
                    break;
                case SyntaxNodeTypes.Add:
                    Write(root.operands[0]);
                    Write(root.operands[1]);
                    code.Add(new Instruction(InstructionCodes.Add, null));
                    break;
                case SyntaxNodeTypes.Sub:
                    Write(root.operands[0]);
                    Write(root.operands[1]);
                    code.Add(new Instruction(InstructionCodes.Sub, null));
                    break;
                case SyntaxNodeTypes.Mul:
                    Write(root.operands[0]);
                    Write(root.operands[1]);
                    code.Add(new Instruction(InstructionCodes.Mul, null));
                    break;
                case SyntaxNodeTypes.Div:
                    Write(root.operands[0]);
                    Write(root.operands[1]);
                    code.Add(new Instruction(InstructionCodes.Div, null));
                    break;
                case SyntaxNodeTypes.Neg:
                    Write(root.operands[0]);
                    code.Add(new Instruction(InstructionCodes.Neg, null));
                    break;
                case SyntaxNodeTypes.Read:
                    Write(root.operands[0]);
                    code.Add(new Instruction(InstructionCodes.Read, null));
                    break;
                case SyntaxNodeTypes.Write:
                    Write(root.operands[0]);
                    code.Add(new Instruction(InstructionCodes.Write, null));
                    break;
                case SyntaxNodeTypes.Assign:
                    if (root.operands[0].nodeType == SyntaxNodeTypes.Variable)
                    {
                        Write(root.operands[1]);
                        pushVal = ConvertValue(root.operands[0].value);
                        code.Add(new Instruction(InstructionCodes.Push, pushVal));
                        if (pushVal is string && ((string)pushVal).StartsWith("$"))
                            code.Add(new Instruction(InstructionCodes.GStore, null));
                        else
                            code.Add(new Instruction(InstructionCodes.Store, null));
                        Write(root.operands[0]);
                    }
                    else if (root.operands[0].nodeType == SyntaxNodeTypes.Table)
                    {
                        Write(root.operands[1]);
                        Write(root.operands[0].operands[1]);
                        Write(root.operands[0].operands[0]);
                        code.Add(new Instruction(InstructionCodes.DStore, null));
                        Write(root.operands[0]);
                    }
                    break;
                case SyntaxNodeTypes.If:
                    string lTrue = GetNextLabel();
                    string lFalse = GetNextLabel();
                    string lEnd = GetNextLabel();
                    Write(root.operands[0]);
                    code.Add(new Instruction(InstructionCodes.Push, lTrue));
                    code.Add(new Instruction(InstructionCodes.Jt, null));
                    code.Add(new Instruction(InstructionCodes.Push, lFalse));
                    code.Add(new Instruction(InstructionCodes.Jmp, null));
                    code.Add(new Instruction(InstructionCodes.Label, lTrue));
                    Write(root.operands[1]);
                    code.Add(new Instruction(InstructionCodes.Push, lEnd));
                    code.Add(new Instruction(InstructionCodes.Jmp, null));
                    code.Add(new Instruction(InstructionCodes.Label, lFalse));
                    Write(root.operands[2]);
                    code.Add(new Instruction(InstructionCodes.Label, lEnd));
                    break;
                case SyntaxNodeTypes.Loop:
                    string lTop = GetNextLabel();
                    code.Add(new Instruction(InstructionCodes.Label, lTop));
                    Write(root.operands[0]);
                    Write(root.operands[1]);
                    code.Add(new Instruction(InstructionCodes.Push, lTop));
                    code.Add(new Instruction(InstructionCodes.Jt, null));
                    break;
                case SyntaxNodeTypes.Eq:
                    Write(root.operands[0]);
                    Write(root.operands[1]);
                    code.Add(new Instruction(InstructionCodes.Eq, null));
                    break;
                case SyntaxNodeTypes.NEq:
                    Write(root.operands[0]);
                    Write(root.operands[1]);
                    code.Add(new Instruction(InstructionCodes.Eq, null));
                    code.Add(new Instruction(InstructionCodes.Not, null));
                    break;
                case SyntaxNodeTypes.Lt:
                    Write(root.operands[0]);
                    Write(root.operands[1]);
                    code.Add(new Instruction(InstructionCodes.Lt, null));
                    break;
                case SyntaxNodeTypes.Ge:
                    Write(root.operands[0]);
                    Write(root.operands[1]);
                    code.Add(new Instruction(InstructionCodes.Lt, null));
                    code.Add(new Instruction(InstructionCodes.Not, null));
                    break;
                case SyntaxNodeTypes.Le:
                    Write(root.operands[0]);
                    Write(root.operands[1]);
                    code.Add(new Instruction(InstructionCodes.Le, null));
                    break;
                case SyntaxNodeTypes.Gt:
                    Write(root.operands[0]);
                    Write(root.operands[1]);
                    code.Add(new Instruction(InstructionCodes.Le, null));
                    code.Add(new Instruction(InstructionCodes.Not, null));
                    break;
                case SyntaxNodeTypes.Not:
                    Write(root.operands[0]);
                    code.Add(new Instruction(InstructionCodes.Not, null));
                    break;
                case SyntaxNodeTypes.Statements:
                    Write(root.operands[0]);
                    if (root.operands.Count > 1)
                        Write(root.operands[1]);
                    break;
                case SyntaxNodeTypes.Discard:
                    Write(root.operands[0]);
                    code.Add(new Instruction(InstructionCodes.Pop, null));
                    break;
                case SyntaxNodeTypes.Function:
                    string lFBegin = GetNextLabel();
                    string lFEnd = GetNextLabel();
                    code.Add(new Instruction(InstructionCodes.Push, lFEnd));
                    code.Add(new Instruction(InstructionCodes.Jmp, null));
                    code.Add(new Instruction(InstructionCodes.Label, lFBegin));
                    Write(root.operands[0]);
                    code.Add(new Instruction(InstructionCodes.Ret, null));
                    code.Add(new Instruction(InstructionCodes.Label, lFEnd));
                    code.Add(new Instruction(InstructionCodes.Push, lFBegin));
                    break;
                case SyntaxNodeTypes.Call:
                    if (root.operands.Count > 1)
                        Write(root.operands[1]);
                    Write(root.operands[0]);
                    code.Add(new Instruction(InstructionCodes.Call, null));
                    break;
                case SyntaxNodeTypes.Arg:
                    code.Add(new Instruction(InstructionCodes.Push, ConvertValue(root.value)));
                    code.Add(new Instruction(InstructionCodes.Store, null));
                    break;
            }
        }
    }
}
